import { Meta } from '@storybook/addon-docs';

<Meta title=" How to build a scene?" />

# How to build a scene?

If you want to add a new scene in the PostHog App frontend, here are 7 easy steps for fun and profit.

But first, you must answer one question: Does your scene depend on an `id` in the URL, like `/dashboard/:id`?

## Option A: I'm buliding a global scene that does not depend on an `id` in the URL.

### 1. Create the component, logic and styles.

Create a component like: `frontend/src/scenes/dashboard/Dashboards.tsx`

```ts
import { dashboardsLogic } from './dashboardsLogic'
import { SceneExport } from 'scenes/sceneTypes'
import { useValues } from 'kea'

export function Dashboards (): JSX.Element {
    const {
        counter
    } = useValues(dashboardsLogic)

    return (
        // TODO: consolidate on a recommended naming convention
        <div className='dashboard-scene'>
            Dashboard Scene {counter}!
        </div>
    )
}

export const scene: SceneExport = {
    component: Dashboards,
    logic: dashboardsLogic,
}
```

Create the logic: `frontend/src/scenes/dashboard/dashboardsLogic.tsx`

```ts
import { kea, reducers, path } from 'kea'

export const dashboardsLogic = kea([
    path(['scenes', 'dashboard', 'dashboardsLogic']),
    reducers({
        counter: [1, {}],
    }),
])
```

Create the styles `frontend/src/scenes/dashboard/Dashboards.scss`.

```ts
.dashboards-scene {
    // put all your styles inside this scope, as everything is global
}
```

Run kea type generation and check, which will created `dashboardsLogicType.ts` and update imports in `dashboardsLogic.tsx`

```bash
pnpm typegen:write && pnpm typescript:check
```

### 2. Add a URL function

in `frontend/src/scenes/urls.ts`

```ts
export const urls = {
    dashboards: () => `/dashboard`,
}
```

### 3. Add a scene to the enum

in `frontend/src/scenes/sceneTypes.ts`

```ts
export enum Scene {
    Dashboards = 'Dashboards',
}
```

### 4. Add a scene configuration and a route to scene mapping

in `frontend/src/scenes/scenes.ts`

```ts
export const sceneConfigurations: Partial<Record<Scene, SceneConfig>> = {
    [Scene.Dashboards]: {
        projectBased: true,
        name: 'Dashboards',
    },
}

export const routes: Record<string, Scene> = {
    [urls.dashboards()]: Scene.Dashboards,
}
```

### 5. Add a scene import

in `frontend/src/scenes/appScenes.ts`

```ts
export const appScenes: Record<Scene, () => any> = {
    [Scene.Dashboards]: () => import('./dashboard/Dashboards'),
}
```

## Option B: My scene depends on an `id` in the URL (`/dashboard/:id`).

### 1. Create the component, logic and styles.

Create a component like: `frontend/src/scenes/dashboard/Dashboard.tsx`

```ts
import { dashboardLogic } from './dashboardLogic'
import { SceneExport } from 'scenes/sceneTypes'
import { useValues } from 'kea'

export const scene: SceneExport = {
    component: Dashboard,
    logic: dashboardLogic,
    // paramsToProps - Convert url _string_ params to logic props.
    // This mounts the right logic with turbo mode before the component renders.
    // This wraps the scene's logic in <BindLogic />
    paramsToProps: ({ params: {id} }:{ params: { id?: string }}) => ({ id: id ? parseInt(id) : 'new' }),
}

export function Dashboard ({ id }: { id?: string } = {}): JSX.Element {
    // dashboardLogic is automatically bound to the props above with BindLogic
    const {
        counter
    } = useValues(dashboardLogic)

    return (
        // TODO: consolidate on a recommended naming convention
        <div className='dashboard-scene'>
            Dashboard Scene {id} {counter}!
        </div>
    )
}
```

Create the logic: `frontend/src/scenes/dashboard/dashboardLogic.tsx`

```ts
import { kea, reducers, path, props, key } from 'kea'

export interface DashboardLogicProps {
    id: number | 'new'
}

export const dashboardLogic = kea([
    props({} as DashboardLogicProps),
    key(({ id }) => id),
    path((id) => ['scenes', 'dashboard', 'dashboardLogic', id]),
    reducers({
        counter: [1, {}],
    }),
])
```

Create the styles `frontend/src/scenes/dashboard/Dashboard.scss`.

```ts
.dashboard-scene {
    // put all your styles inside this scope, as everything is global
}
```

Run kea type generation and check, which will created `dashboardLogicType.ts` and update imports in `dashboardLogic.tsx`

```bash
pnpm typegen:write && pnpm typescript:check
```

### 2. Add a URL function

in `frontend/src/scenes/urls.ts`

```ts
export const urls = {
    dashboard: (id: string | number) => `/dashboard{id ? `/${id}` : ''}`,
}
```

### 3. Add a scene to the enum

in `frontend/src/scenes/sceneTypes.ts`

```ts
export enum Scene {
    Dashboard = 'Dashboard',
}
```

### 4. Add a scene configuration and a route to scene mapping

in `frontend/src/scenes/scenes.ts`

```ts
export const sceneConfigurations: Partial<Record<Scene, SceneConfig>> = {
    [Scene.Dashboard]: {
        projectBased: true,
        name: 'Dashboard',
    },
}

export const routes: Record<string, Scene> = {
    // this `:id` gets used in "params" in "paramsToProps" and passed to the <Dashboard /> component
    [urls.dashboard(':id')]: Scene.Dashboard,
}
```

### 5. Add a scene import

in `frontend/src/scenes/appScenes.ts`

```ts
export const appScenes: Record<Scene, () => any> = {
    [Scene.Dashboard]: () => import('./dashboard/Dashboard'),
}
```

## 6. Create a story for your scene

In the same folder as your component, create a file like `frontend/src/scenes/dashboard/Dashboard.stories.tsx`.
If you need a lot of `.json` files for mocked data, create a `__mocks__` subdirectory for those files, just like you would with `jest`.

```tsx
import { useEffect } from 'react'
import { Meta, StoryObj } from '@storybook/react'
import { mswDecorator, useStorybookMocks } from '~/mocks/browser'
import { App } from 'scenes/App'
import { router } from 'kea-router'
import { urls } from 'scenes/urls'

export default {
    title: 'Scenes-App/Dashboard',
    decorators: [
        // mocks used by all stories in this file
        mswDecorator({
            get: {
                '/api/projects/1/dashboards/': require('./__mocks__/dashboards.json'),
                '/api/projects/1/dashboards/1/': require('./__mocks__/dashboard1.json'),
                '/api/projects/1/dashboards/1/collaborators/': [],
            },
        }),
    ],
    // NB! These `parameters` only apply for Scene stories.
    parameters: { layout: 'fullscreen', options: { showPanel: false }, viewMode: 'story' }, // scene mode
} as Meta

export function NewDashboard (): JSX.Element {
    // mocks used only in this story
    useStorybookMocks({
        get: { '/api/projects/dashboard/2/': require('./__mocks__/dashboard2.json') },
    })
    useEffect(() => {
        // change the URL
        router.actions.push(urls.dashboard(2))
        // call various other actions to set the initial state
        newDashboardLogic.actions.showNewDashboardModal()
    }, [])
    return <App />
}
```

## 7. Add some components

Read next:
- [How to use components](/docs/how-to-use-components--page)
- [How to build a form](/docs/how-to-build-a-form--page)
- [How to add tabs to a scene](/docs/how-to-add-tabs-to-a-scene--page)
